﻿// Copyright © Microsoft Corporation.  All Rights Reserved.
// This code released under the terms of the 
// Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.)

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Media.Animation;

namespace TheOliver.Controls
{
    #region New Version

    public class DeepZoomRandomImageArrangeBehavior : TargetedTriggerAction<MultiScaleImage>
    {
        private List<MultiScaleSubImage> _randomList = new List<MultiScaleSubImage>();
        private MultiScaleImage _msi;
        
        #region Properties

        public double XOffset
        {
            get { return (double)GetValue(XOffsetProperty); }
            set { SetValue(XOffsetProperty, value); }
        }

        public static readonly DependencyProperty XOffsetProperty =
            DependencyProperty.Register(
                "XOffset", typeof(double),
                typeof(DeepZoomRandomImageArrangeBehavior),
                new PropertyMetadata(0.01, XOffsetChanged));

        private static void XOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            DeepZoomRandomImageArrangeBehavior beh = d as DeepZoomRandomImageArrangeBehavior;
            double v = (double)e.NewValue;
            if (v > 1.0)
            {
                beh.XOffset= 1.0;
            }
            else if (v < 0)
            {
                beh.XOffset = 0.0;
            }
        }

        public double YOffset
        {
            get { return (double)GetValue(YOffsetProperty); }
            set { SetValue(YOffsetProperty, value); }
        }

        public static readonly DependencyProperty YOffsetProperty =
            DependencyProperty.Register(
                "YOffset",
                typeof(double),
                typeof(DeepZoomRandomImageArrangeBehavior),
                new PropertyMetadata(0.01, YOffsetChanged));

        private static void YOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            DeepZoomRandomImageArrangeBehavior beh = d as DeepZoomRandomImageArrangeBehavior;
            double v = (double)e.NewValue;
            if (v > 1.0)
            {
                beh.YOffset = 1.0;
            }
            else if (v < 0)
            {
                beh.YOffset = 0.0;
            }
        }

        public int Speed
        {
            get { return (int)GetValue(SpeedProperty); }
            set { SetValue(SpeedProperty, value); }
        }

        public static readonly DependencyProperty SpeedProperty =
            DependencyProperty.Register(
                "Speed",
                typeof(int),
                typeof(DeepZoomRandomImageArrangeBehavior),
                new PropertyMetadata(1000, SpeedChanged));

        private static void SpeedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {

        }

        public int AnimationTimeInSeconds
        {
            get { return (int)GetValue(AnimationTimeInSecondsProperty); }
            set { SetValue(AnimationTimeInSecondsProperty, value); }
        }

        // Using a DependencyProperty as the backing store for AnimationTimeInSeconds.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AnimationTimeInSecondsProperty =
            DependencyProperty.Register(
                "AnimationTimeInSeconds", 
                typeof(int), 
                typeof(DeepZoomRandomImageArrangeBehavior), 
                new PropertyMetadata(2));

        #endregion

        protected override void Invoke(object o)
        {
            _msi = this.Target;
            ArrangeIntoGrid();
        }
        
        private void ArrangeIntoGrid()
        {
            TimeSpan animationTimeSpan = new TimeSpan(0, 0, this.AnimationTimeInSeconds);
            List<MultiScaleSubImage> images = RandomizedListOfImages();

            double containerAspectRatio = _msi.ActualWidth / _msi.ActualHeight;
            if (_msi.SubImages.Count == 0)
            {
                return;
            }

            List<HelperSubImage> subImages = new List<HelperSubImage>();
            foreach (var si in images)
            {
                subImages.Add(new HelperSubImage(si));
            }

            // Capture the total width of all images
            double totalImagesWidth = 0.0;
            foreach (HelperSubImage subImage in subImages)
            {
                totalImagesWidth += subImage.Width;
            }

            // Calculate the total number of rows required to display all the images
            int numRows = (int)Math.Sqrt((totalImagesWidth / containerAspectRatio) + 1);

            // Assign images to each row
            List<HelperRow> rows = new List<HelperRow>(numRows);
            for (int i = 0; i < numRows; i++)
            {
                HelperRow row = new HelperRow(XOffset);
                rows.Add(row);
            }

            double widthPerRow = totalImagesWidth / numRows;
            double imagesWidth = 0;
            for (int i = 0, j = 0; i < numRows; i++, imagesWidth = 0)
            {
                while (imagesWidth < widthPerRow && j < subImages.Count)
                {
                    rows[i].AddImage(subImages[j]);
                    subImages[j].RowNum = i;
                    imagesWidth += subImages[j++].Width;
                }
            }

            foreach (var row in rows)
            {
                row.Scale(containerAspectRatio);
            }
            //rows.ForEach(Row => Row.Scale(ContainerAspectRatio));

            double totalImagesHeight = (numRows - 1) * XOffset;
            foreach (var row in rows)
            {
                totalImagesHeight += row.Height;
            }

            if (totalImagesHeight > 1)
            {
                foreach (var subImage in subImages)
                {
                    double scaleFactor = 1 / (totalImagesHeight + XOffset);
                    subImage.Scale(scaleFactor);
                }
                //subImages.ForEach(subImage => subImage.Scale(1 / (totalImagesHeight + XOffset)));
                totalImagesHeight = (numRows - 1) * XOffset;
                foreach (var row in rows)
                {
                    totalImagesHeight += row.Height;
                }
                // rows.ForEach(Row => totalImagesHeight += Row.Height);
            }

            // Calculate the top and bottom margin
            double margin = (1 - totalImagesHeight) / 2;

            # region Create Storyboard and remove old if exist

            string storyboardKey = "rectLayoutStoryBoard";
            Storyboard storyboard = new Storyboard();
            storyboard.Completed += (sender, e) =>
            {
                _msi.Resources.Remove(storyboardKey);
            };

            if (_msi.Resources.Contains(storyboardKey))
            {
                _msi.Resources.Remove(storyboardKey);
            }
            _msi.Resources.Add(storyboardKey, storyboard);

            List<Point> futurePoints = new List<Point>();
            for (int i = 0; i < subImages.Count; i++)
            {
                double X = rows[subImages[i].RowNum].CalcX(subImages[i].ColNum);
                double Y = margin;

                for (int j = 0; j < subImages[i].RowNum; j++)
                {
                    Y += XOffset + rows[j].Height;
                }

                double viewportWidth = containerAspectRatio / subImages[i].Width;
                //images[i].ViewportWidth = viewportWidth;

                Point futurePosition = new Point(-(X / subImages[i].Width), -(Y / subImages[i].Width));
                AnimateImage(storyboard, images[i], futurePosition, animationTimeSpan, viewportWidth);
                futurePoints.Add(futurePosition);
            }

            #endregion

            storyboard.Begin();
        }

        internal void AnimateImage(Storyboard storyboard, MultiScaleSubImage currentImage, Point futurePosition, 
            TimeSpan animationTimeSpan, double viewPortWidth)
        {
            // Create Keyframe
            SplinePointKeyFrame endKeyframe = new SplinePointKeyFrame();
            endKeyframe.Value = futurePosition;
            endKeyframe.KeyTime = KeyTime.FromTimeSpan(animationTimeSpan);

            KeySpline ks = new KeySpline();
            ks.ControlPoint1 = new Point(0, 1);
            ks.ControlPoint2 = new Point(1, 1);
            endKeyframe.KeySpline = ks;

            // Create Move Animation
            PointAnimationUsingKeyFrames moveAnimation = new PointAnimationUsingKeyFrames();
            moveAnimation.KeyFrames.Add(endKeyframe);

            Storyboard.SetTarget(moveAnimation, currentImage);
            Storyboard.SetTargetProperty(moveAnimation, new PropertyPath("ViewportOrigin"));
            storyboard.Children.Add(moveAnimation);

            // Resize Animation
            SplineDoubleKeyFrame endKeyframe2 = new SplineDoubleKeyFrame();
            endKeyframe2.Value = viewPortWidth;
            endKeyframe2.KeyTime = KeyTime.FromTimeSpan(animationTimeSpan);

            KeySpline ks2 = new KeySpline();
            ks2.ControlPoint1 = new Point(0, 1);
            ks2.ControlPoint2 = new Point(1, 1);
            endKeyframe2.KeySpline = ks2;

            DoubleAnimationUsingKeyFrames sizeAnimation = new DoubleAnimationUsingKeyFrames();
            sizeAnimation.KeyFrames.Add(endKeyframe2);

            Storyboard.SetTarget(sizeAnimation, currentImage);
            Storyboard.SetTargetProperty(sizeAnimation, new PropertyPath("ViewportWidth"));
            storyboard.Children.Add(sizeAnimation);
        }

        private List<MultiScaleSubImage> RandomizedListOfImages()
        {
            List<MultiScaleSubImage> imageList = new List<MultiScaleSubImage>();
            Random ranNum = new Random();

            // Store List of Images
            foreach (MultiScaleSubImage subImage in _msi.SubImages)
            {
                subImage.Opacity = 1;
                imageList.Add(subImage);
            }

            int numImages = imageList.Count;

            // Randomize Image List
            for (int a = 0; a < numImages; a++)
            {
                MultiScaleSubImage tempImage = imageList[a];
                imageList.RemoveAt(a);
                int ranNumSelect = ranNum.Next(imageList.Count);
                imageList.Insert(ranNumSelect, tempImage);
            }

            return imageList;
        }
    }

    #endregion

    public class HelperSubImage
    {
        private Size _imageSize = new Size(0.0, 1.0);	//normalized height is 1 for all images
        private int _colNum = 0;
        private int _rowNum = 0;

        internal HelperSubImage(MultiScaleSubImage image)
        {
            _imageSize.Width = image.AspectRatio;
        }

        internal int ColNum
        {
            get { return _colNum; }
            set { _colNum = value; }
        }

        internal int RowNum
        {
            get { return _rowNum; }
            set { _rowNum = value; }
        }

        internal double Width
        {
            get { return _imageSize.Width; }
        }

        internal double Height
        {
            get { return _imageSize.Height; }
        }

        internal void Scale(double scaleBy)
        {
            //_imageSize.Width *= scaleBy;
            //_imageSize.Height *= scaleBy;

            _imageSize.Width *= Math.Abs(scaleBy);
            _imageSize.Height *= Math.Abs(scaleBy);
        }
    }

    public class HelperRow
    {
        List<HelperSubImage> _subImages = new List<HelperSubImage>();
        double _XOffset = 0;

        internal HelperRow(double spaceBetweenImage)
        {
            _XOffset = spaceBetweenImage;
        }

        internal double TotalWidth
        {
            get
            {
                double totalWidth = 0;
                foreach (var subImage in _subImages)
                {
                    totalWidth += subImage.Width;
                }
                //_subImages.ForEach(image => totalWidth += image.Width);
                return totalWidth;
            }
        }

        internal int ImageCount
        {
            get
            {
                return _subImages.Count;
            }
        }

        internal double Height
        {
            get
            {
                return (ImageCount <= 0) ? 0 : _subImages[0].Height;
            }
        }

        internal double TotalXOffset
        {
            get
            {
                double result = (ImageCount <= 0) ? 0 : (ImageCount - 1) * _XOffset;
                return result;
            }
        }

        internal void AddImage(HelperSubImage subImage)
        {
            _subImages.Add(subImage);
            subImage.ColNum = _subImages.Count - 1;
        }

        internal void Scale(double canvasWidth)
        {
            double scaleBy = (canvasWidth - TotalXOffset) / TotalWidth;
            foreach (HelperSubImage subImage in _subImages)
            {
                subImage.Scale(scaleBy);
            }
        }

        internal double CalcX(int colNum)
        {
            Debug.Assert(colNum < _subImages.Count);

            double X = 0;
            for (int i = 0; i < colNum; i++)
            {
                X += _subImages[i].Width + _XOffset;
            }
            return X;
        }
    }

    #region Old Version

    //public class DeepZoomRandomImageArrangeBehaviorOld : TargetedTriggerAction<MultiScaleImage>
    //{
    //    private List<MultiScaleSubImage> _randomList = new List<MultiScaleSubImage>();

    //    int _totalColumns = 0;
    //    int _totalRows = 0;

    //    private MultiScaleImage _msi;

    //    public double XOffset
    //    {
    //        get { return (double)GetValue(XOffsetProperty); }
    //        set { SetValue(XOffsetProperty, value); }
    //    }

    //    public static readonly DependencyProperty XOffsetProperty =
    //        DependencyProperty.Register(
    //            "XOffset", typeof(double),
    //            typeof(DeepZoomRandomImageArrangeBehavior),
    //            new PropertyMetadata(1.25, XOffsetChanged));

    //    private static void XOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    //    {

    //    }

    //    public double YOffset
    //    {
    //        get { return (double)GetValue(YOffsetProperty); }
    //        set { SetValue(YOffsetProperty, value); }
    //    }

    //    public static readonly DependencyProperty YOffsetProperty =
    //        DependencyProperty.Register(
    //            "YOffset",
    //            typeof(double),
    //            typeof(DeepZoomRandomImageArrangeBehavior),
    //            new PropertyMetadata(1.25, YOffsetChanged));

    //    private static void YOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    //    {

    //    }

    //    public int Speed
    //    {
    //        get { return (int)GetValue(SpeedProperty); }
    //        set { SetValue(SpeedProperty, value); }
    //    }

    //    public static readonly DependencyProperty SpeedProperty =
    //        DependencyProperty.Register(
    //            "Speed",
    //            typeof(int),
    //            typeof(DeepZoomRandomImageArrangeBehavior),
    //            new PropertyMetadata(1000, SpeedChanged));

    //    private static void SpeedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    //    {

    //    }

    //    protected override void Invoke(object o)
    //    {
    //        _msi = this.Target;
    //        ArrangeIntoGrid();
    //    }

    //    private void ArrangeIntoGrid()
    //    {
    //        _randomList = RandomizedListOfImages();

    //        int totalImagesAdded = 0;
    //        double totalWidth = 0;
    //        double XOffset = XOffset;
    //        double yoffset = YOffset;
    //        double margin = .1;

    //        if (_randomList.Count > 1)
    //        {
    //            int numberOfImages = _randomList.Count;
    //            _totalColumns = 0;
    //            _totalColumns = (int)Math.Ceiling(Math.Sqrt(numberOfImages));
    //            _totalRows = numberOfImages / (_totalColumns - 1);
    //            totalWidth = _totalColumns * XOffset;

    //            Storyboard moveStoryboard = new Storyboard();

    //            for (int row = 0; row < _totalRows; row++)
    //            {
    //                for (int col = 0; col < _totalColumns; col++)
    //                {
    //                    if (numberOfImages != totalImagesAdded)
    //                    {
    //                        MultiScaleSubImage currentImage = _randomList[totalImagesAdded];
    //                        currentImage.ViewportWidth = 1;

    //                        Point currentPosition = new Point(0, 0);
    //                        Point futurePosition = new Point(-XOffset * col, -yoffset * row);

    //                        // Create Animation
    //                        PointAnimationUsingKeyFrames moveAnimation = new PointAnimationUsingKeyFrames();

    //                        // Create Keyframe
    //                        SplinePointKeyFrame startKeyframe = new SplinePointKeyFrame();
    //                        startKeyframe.Value = currentPosition;
    //                        startKeyframe.KeyTime = KeyTime.FromTimeSpan(TimeSpan.Zero);

    //                        startKeyframe = new SplinePointKeyFrame();
    //                        startKeyframe.Value = futurePosition;
    //                        startKeyframe.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(Speed));

    //                        KeySpline ks = new KeySpline();
    //                        ks.ControlPoint1 = new Point(0, 1);
    //                        ks.ControlPoint2 = new Point(1, 1);
    //                        startKeyframe.KeySpline = ks;
    //                        moveAnimation.KeyFrames.Add(startKeyframe);

    //                        Storyboard.SetTarget(moveAnimation, currentImage);

    //                        Storyboard.SetTargetProperty(moveAnimation, new PropertyPath("ViewportOrigin"));

    //                        moveStoryboard.Children.Add(moveAnimation);

    //                        totalImagesAdded++;
    //                    }
    //                    else
    //                    {
    //                        break;
    //                    }
    //                }
    //            }

    //            if (!_msi.Resources.Contains("myAnim"))
    //            {
    //                _msi.Resources.Add("myAnim", moveStoryboard);
    //            }

    //            this._msi.ViewportOrigin = new Point(-margin, -margin);
    //            this._msi.ViewportWidth = totalWidth + margin;

    //            // Play Storyboard
    //            moveStoryboard.Begin();
    //        }
    //        else
    //        {
    //            MultiScaleSubImage currentImage = _randomList[0];
    //            currentImage.ViewportWidth = 1 + 2 * margin;
    //            currentImage.ViewportOrigin = new Point(0, 0);
    //            this._msi.ViewportOrigin = new Point(-margin, -margin);
    //            this._msi.ViewportWidth = 1;
    //        }
    //    }

    //    private List<MultiScaleSubImage> RandomizedListOfImages()
    //    {
    //        List<MultiScaleSubImage> imageList = new List<MultiScaleSubImage>();
    //        Random ranNum = new Random();

    //        // Store List of Images
    //        foreach (MultiScaleSubImage subImage in _msi.SubImages)
    //        {
    //            subImage.Opacity = 1;
    //            imageList.Add(subImage);
    //        }

    //        int numImages = imageList.Count;

    //        // Randomize Image List
    //        for (int a = 0; a < numImages; a++)
    //        {
    //            MultiScaleSubImage tempImage = imageList[a];
    //            imageList.RemoveAt(a);

    //            int ranNumSelect = ranNum.Next(imageList.Count);

    //            imageList.Insert(ranNumSelect, tempImage);
    //        }

    //        return imageList;
    //    }
    //}
    #endregion

}